package es.ucm.fdi.pe;

import es.ucm.fdi.pe.ConfigPanel.ChoiceOption;
import es.ucm.fdi.pe.ConfigPanel.DoubleOption;
import es.ucm.fdi.pe.ConfigPanel.InnerOption;
import es.ucm.fdi.pe.ConfigPanel.IntegerOption;
import es.ucm.fdi.pe.ConfigPanel.StrategyOption;
import genetica.Start;
import genetica.algoritmoSeleccion.ASRanking;
import genetica.algoritmoSeleccion.ASRuleta;
import genetica.algoritmoSeleccion.ASTorneo;
import genetica.algoritmoSeleccion.AlgoritmoSeleccion;
import genetica.cromosomas.Cromosoma;
import genetica.cromosomas.evaluadores.EvaluadorViajante;
import genetica.cromosomas.factorias.FactoriaCromosomas;
import genetica.cromosomas.factorias.FactoriaViajante;
import genetica.cruce.AlgoritmoCruce;
import genetica.cruce.CruceCX;
import genetica.cruce.CruceCodificacionOrdinal;
import genetica.cruce.CruceERX;
import genetica.cruce.CruceInserccionAlFinal;
import genetica.cruce.CruceOX;
import genetica.cruce.CrucePMX;
import genetica.cruce.CruceVarianteOX;
import genetica.data.viajante.Ruta1;
import genetica.data.viajante.Ruta2;
import genetica.data.viajante.RutasInvalidas;
import genetica.excepciones.ExcepcionDeRango;
import genetica.excepciones.ExcepcionGenetica;
import genetica.mutacion.AlgoritmoMutacion;
import genetica.mutacion.MutacionAGenProximo;
import genetica.mutacion.MutacionHeuristica;
import genetica.mutacion.MutacionInserccion;
import genetica.mutacion.MutacionInversion;
import genetica.mutacion.MutacionPorIntercambio;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

import org.math.plot.Plot2DPanel;

public class Gui extends JFrame {

	private static final long serialVersionUID = 5393378737313833016L;
	protected Datos datos = null;

	public Gui() {
		super("Algoritmos Genéticos");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLayout(new BorderLayout());
		JPanel panelCentral = new JPanel(new GridLayout(2, 1));
		this.add(panelCentral);

		this.datos = new Datos();
		// crea un panel central y lo asocia con la primera figura
		final ConfigPanel<Datos> cp = creaPanelConfiguracion();
		// asocia el panel con la figura
		cp.setTarget(this.datos);
		// carga los valores de la figura en el panel
		cp.initialize();
		this.add(cp, BorderLayout.NORTH);

		// usado por todos los botones
		JButton boton;

		// crea botones para mostrar el estado de las figuras por consola
		boton = new JButton("Mostrar Gráfica");
		boton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				try {
					Cromosoma.setMaximizar(datos.maximizar);
					EvaluadorViajante.rutas = datos.rutas;
					Start.startAlgoritm(datos.tamPob, datos.maxGen,
							datos.probCruce, datos.probMutacion, datos.funcion,
							datos.as, datos.am,
							datos.ac,
							datos.elitista);
					JFrame Ventana = new JFrame();
					Ventana.add(getGraphicPanel());
					Ventana.setSize(800, 600);
					Ventana.setVisible(true);
				} catch (ExcepcionDeRango e) {
					if (e instanceof ExcepcionDeRango)
						;
					JOptionPane.showMessageDialog(null,
							"Los rangos de la aplicacion no son validos, purba con otros\n"
									+ "Valor Superior: " + e.getTop() + "\n"
									+ "Valor Inferior: " + e.getFloor() + "\n"
									+ "Valor de la función: " + e.getValue());

				} catch (ExcepcionGenetica e) {
					e.printStackTrace();
				}

			}
		});
		this.add(boton, BorderLayout.SOUTH);

	}

	public Datos getDatos() {
		return this.datos;
	}

	@SuppressWarnings("unchecked")
	public ConfigPanel<Datos> creaPanelConfiguracion() {
		FactoriaCromosomas<Cromosoma>[] funciones = new FactoriaCromosomas[] { new FactoriaViajante(
				0) };
		// String[] algoritmoSeleccion = new String[] { new String("Ruleta"),
		// new String("Toreno")};

		AlgoritmoSeleccion[] algoritmoSeleccion = new AlgoritmoSeleccion[] {
				new ASRuleta(), 
				new ASTorneo(datos.probabilista, datos.p),
				new ASRanking(datos.beta) };
		AlgoritmoMutacion[] algoritmoMutacion = new AlgoritmoMutacion[] {
				new MutacionInserccion(datos.probMutacion),
				new MutacionHeuristica(datos.probMutacion,
						datos.genesHeuristica),
				new MutacionPorIntercambio(datos.probMutacion),
				new MutacionInversion(datos.probMutacion),
				new MutacionAGenProximo(datos.probMutacion, datos.rango, datos.probCercano)};
		AlgoritmoCruce[] algoritmoCruce = new AlgoritmoCruce[] {
				new CruceCodificacionOrdinal(datos.probCruce),
				new CruceCX(datos.probCruce), new CruceERX(datos.probCruce),
				new CruceOX(datos.probCruce), new CrucePMX(datos.probCruce),
				new CruceVarianteOX(datos.probCruce),
				new CruceInserccionAlFinal(datos.probCruce)};
		RutasInvalidas[] rutasInvalidas = new RutasInvalidas[]{
				new Ruta1(),
				new Ruta2()
		};
		ConfigPanel<Datos> config = new ConfigPanel<Datos>();

		config.addOption(new IntegerOption<Datos>( // -- entero
				"Tamaño de la población Población", "tamaño de la población",

				"tamPob", 10, 1000))
				// min y max (usa Integer.MIN_VALUE /MAX_VALUE para
				// infinitos)
				.addOption(new IntegerOption<Datos>( // -- entero
						"Máximas generaciones", // texto a usar como etiqueta
												// del campo
						"Número máximo de generaciones", // texto a usar como
															// 'tooltip' cuando
															// pasas el puntero
						"maxGen", // campo
						0, 10000))
				// min y max (usa Integer.MIN_VALUE /MAX_VALUE
				// para infinitos)
				.addOption(new DoubleOption<Datos>( // -- doble, parecido a
													// entero
						"Probabilidad de cruce", // etiqueta
						"probabilidad de cruce", // tooltip
						"probCruce", // campo
						0, 100))
				// min y max, aplicando factor, si hay; vale
				// usar Double.*_INFINITY)
				.addOption(new DoubleOption<Datos>( // -- doble, parecido a
													// entero
						"Probabilidad de Mutación", // etiqueta
						"probabilidad de Mutación", // tooltip
						"probMutacion", // campo
						0, 100))
				.addOption(
						new IntegerOption<Datos>(
								"Cromosomas para la elite",
								"Cromosomas que participan en el elitismo, entre 1 y el maximo de población, 0 significa sin elitismo",
								"elitista", // campo
								0, Integer.MAX_VALUE))
				// min y max, aplicando factor, si hay; vale
				// usar Double.*_INFINITY)
				// .addOption(new DoubleOption<Datos>( // -- doble, parecido a
				// // entero
				// "Precision", // etiqueta
				// "precision", // tooltip
				// "precision", // campo
				// 0, 100))
				// min y max, aplicando factor, si hay; vale
				// usar Double.*_INFINITY)
				// .addOption(new DoubleOption<Datos>( // -- doble, parecido a
				// // entero
				// "Tolerancia", // etiqueta
				// "transparencia del borde", // tooltip
				// "tolerancia", // campo
				// 0, 100))
				// min y max, aplicando factor, si hay; vale
				// usar Double.*_INFINITY)
				.addOption(
						new ChoiceOption<Datos>("Maximizar funcion?",
								"Desea maximizar la funcion", "maximizar",
								new Boolean[] { new Boolean(true),
										new Boolean(false) }))
				.addOption(new StrategyOption<FactoriaCromosomas<Cromosoma>>( // --
																				// eleccion
																				// de
																				// objeto
						"Función", // etiqueta
						"función a optimizar", // tooltip
						"funcion", // campo
						funciones))
				// .beginInner(
				// new InnerOption<Container, FactoriaCromosomas<Cromosoma>>(
				// "Parametros extra de la funcion",
				// "Parametros de las funciones", "funcion",
				// factoriaFuncionD.class))
				// .addInner(
				// new IntegerOption<Datos>("n",
				// "Número de repeticiones del sumatorio", "n", 1,
				// Integer.MAX_VALUE))
				// .addInner(new DoubleOption<Datos>( // -- doble, parecido a
				// // entero
				// "Precision del resultado", // etiqueta
				// "Indica la precision del resultado", // tooltip
				// "precision", // campo
				// 0.00001, 1))
				// .endInner()
				//
				// .beginInner(
				// new InnerOption<Container, FactoriaCromosomas<Cromosoma>>(
				// "Parametros extra de la funcion",
				// "Parametros de las funciones", "funcion",
				// factoriaFuncionA.class))
				// .addInner(new DoubleOption<Datos>( // -- doble, parecido a
				// // entero
				// "Precision del resultado", // etiqueta
				// "Indica la precision del resultado", // tooltip
				// "precision", // campo
				// 0.00001, 1))
				// .endInner()
				// .beginInner(
				// new InnerOption<Container, FactoriaCromosomas<Cromosoma>>(
				// "Parametros extra de la funcion",
				// "Parametros de las funciones", "funcion",
				// factoriaFuncionB.class))
				// .addInner(new DoubleOption<Datos>( // -- doble, parecido a
				// // entero
				// "Precision del resultado", // etiqueta
				// "Indica la precision del resultado", // tooltip
				// "precision", // campo
				// 0.00001, 1))
				// .endInner()
				// .beginInner(
				// new InnerOption<Container, FactoriaCromosomas<Cromosoma>>(
				// "Parametros extra de la funcion",
				// "Parametros de las funciones", "funcion",
				// factoriaFuncionC.class))
				// .addInner(new DoubleOption<Datos>( // -- doble, parecido a
				// // entero
				// "Precision del resultado", // etiqueta
				// "Indica la precision del resultado", // tooltip
				// "precision", // campo
				// 0.00001, 1))
				// .endInner()
				// .beginInner(
				// new InnerOption<Container, FactoriaCromosomas<Cromosoma>>(
				// "Parametros extra de la funcion",
				// "Parametros de las funciones", "funcion",
				// factoriaFuncionE.class))
				// .addInner(new DoubleOption<Datos>( // -- doble, parecido a
				// // entero
				// "Precision del resultado", // etiqueta
				// "Indica la precision del resultado", // tooltip
				// "precision", // campo
				// 0.00001, 1))
				// .endInner()

				// elecciones posibles
				.addOption(
						new StrategyOption<RutasInvalidas>(
								"Mada de rutas invalidas",
								"Indica la relaccion de ciudades y dias invalidos", "rutas",
								rutasInvalidas))
				.addOption(
						new StrategyOption<AlgoritmoMutacion>(
								"Algoritmo de mutación",
								"Algoritmo de mutación", "am",
								algoritmoMutacion))
				.beginInner(
						new InnerOption<Container, AlgoritmoMutacion>(
								"Algorimo de Selección por rankin",
								"opciones del algoritmo de selección por rankin",
								"am", MutacionHeuristica.class))
				.addInner(
						new IntegerOption<Datos>(
								"Número de cromosomas",
								"Número de cromosomas que participan en el torneo",
								"genesHeuristica", 2, Integer.MAX_VALUE))
				.endInner()
				.beginInner(
						new InnerOption<Container, AlgoritmoMutacion>(
								"Algoritmo de muración a gen proximo",
								"Algoritmo que muta los genes dependiendo de la distancia relativa entre los genes",
								"am", MutacionAGenProximo.class))
				.addInner(
						new IntegerOption<Datos>(
								"Rango de los genes",
								"Número de genes que participan en la mutacion",
								"rango", 1, Integer.MAX_VALUE))
				.addInner(
						new DoubleOption<Datos>(
								"Probabilidad de seleccionar genes cercanos",
								"Probabilidad de que los genes que participan en la mutacion sean los mas afines y no al contrario",
								"probCercano", 0, 1))
				.endInner()
				.addOption(
						new StrategyOption<AlgoritmoCruce>(
								"Algoritmo de cruce", "Algoritmo de cruce",
								"ac", algoritmoCruce))
				.addOption(
						new StrategyOption<AlgoritmoSeleccion>(
								// --
								// eleccion
								// de objeto
								// configurable
								"Algoritmo de selección", // etiqueta
								"Algoritmo de selección utilizado por el algoritmo genético", // tooltip
								"as", // campo
								algoritmoSeleccion))
				.beginInner(
						new InnerOption<Container, AlgoritmoSeleccion>(
								"Algorimo de Selección por torneo",
								"opciones del algoritmo de selección por torneo",
								"as", ASTorneo.class))
				.addInner(
						new IntegerOption<Datos>(
								"Número de cromosomas",
								"Número de cromosomas que participan en el torneo",
								"p", 2, Integer.MAX_VALUE))
				.addInner(
						new ChoiceOption<Datos>(
								"probabilista",
								"Indica si el algoritmo de seleccion es probabilista o por el contrario determinista",
								"probabilista", new Boolean[] {
										new Boolean(true), new Boolean(false) }))
				.endInner()
				.beginInner(
						new InnerOption<Container, AlgoritmoSeleccion>(
								"Algorimo de Selección por rankin",
								"opciones del algoritmo de selección por rankin",
								"as", ASRanking.class))
				.addInner(
						new DoubleOption<Datos>(
								"Número de cromosomas",
								"Número de cromosomas que participan en el torneo",
								"beta", 1, 2))
				.endInner()
				.endOptions();

		return config;
	}

	// construye y muestra la interfaz
	public static void main(String[] args) {

		Gui p = new Gui();
		p.setSize(700, 500);
		p.setVisible(true);
	}

	public static class Datos {
		private int tamPob = 10; // tamaño de la población
		private int maxGen = 100; // limite de iteraciones
		private double probCruce = 0.8; // probabilidad de cruce
		private double probMutacion = 0.005; // probabilidad de mutación
//		private double probMutacion = 0.0; // probabilidad de mutación
		private double precision = 0.001;
		private double tolerancia = 0;
		private int genesHeuristica = 3;
		private boolean probabilista = false;
		private int p = 2;
		private RutasInvalidas rutas = new Ruta1();
		private FactoriaCromosomas<Cromosoma> funcion = (FactoriaCromosomas<Cromosoma>) new FactoriaViajante(
				0); // funcion seleccionada
		private double beta = 1.5;
		private AlgoritmoSeleccion as = new ASRuleta(); // Algoritmo de
														// seleccion
//		private AlgoritmoMutacion am = new MutacionInserccion(probMutacion);
		private int rango = tamPob/3;
		private double probCercano = 0.95;

		private AlgoritmoMutacion am = new MutacionAGenProximo(probMutacion,rango,probCercano);
		private AlgoritmoCruce ac = new CruceERX(probCruce);

		private Integer elitista = 0;

		private boolean maximizar = false;
		

		public int getGenesHeuristica() {
			return this.genesHeuristica;
		}

		public void setGenesHeuristica(int genesHeuristica) {
			this.genesHeuristica = genesHeuristica;
		}

		// getters y setters
		public int getTamPob() {
			return this.tamPob;
		}

		public int getMaxGen() {
			return this.maxGen;
		}

		public double getProbMutacion() {
			return this.probMutacion;
		}

		public double getProbCruce() {
			return this.probCruce;
		}

		public double getPrecision() {
			return this.precision;
		}

		public double getTolerancia() {
			return this.tolerancia;
		}

		public FactoriaCromosomas<Cromosoma> getFuncion() {
			return this.funcion;
		}

		public AlgoritmoSeleccion getAs() {
			return this.as;
		}

		public boolean isProbabilista() {
			return probabilista;
		}

		public int getP() {
			return p;
		}

		public void setTamPob(int tamPob) {
			this.tamPob = tamPob;
		}

		public void setMaxGen(int maxGen) {
			this.maxGen = maxGen;
		}

		public void setProbMutacion(double probMut) {
			this.probMutacion = probMut;
		}

		public void setProbCruce(double probCruce) {
			this.probCruce = probCruce;
		}

		public void setPrecision(double precision) {
			this.precision = precision;
		}

		public void setTolerancia(double tolerancia) {
			this.tolerancia = tolerancia;
		}

		public void setFuncion(FactoriaCromosomas<Cromosoma> funcion) {
			this.funcion = funcion;
		}

		public void setAs(AlgoritmoSeleccion as) {
			this.as = as;
		}

		public void setProbabilista(boolean probabilista) {
			this.probabilista = probabilista;
		}

		public void setP(int p) {
			this.p = p;
		}

		public void setElitista(Integer elitista) {
			this.elitista = elitista;
		}

		public Integer getElitista() {
			return elitista;
		}

		public void setMaximizar(boolean maximizar) {
			this.maximizar = maximizar;
		}

		public boolean getMaximizar() {
			return maximizar;
		}

		public void setAm(AlgoritmoMutacion am) {
			this.am = am;
		}

		public AlgoritmoMutacion getAm() {
			return am;
		}

		public void setBeta(double beta) {
			this.beta = beta;
		}

		public double getBeta() {
			return beta;
		}

		public void setAc(AlgoritmoCruce ac) {
			this.ac = ac;
		}

		public AlgoritmoCruce getAc() {
			return ac;
		}

		public void setRango(int rango) {
			this.rango = rango;
		}

		public int getRango() {
			return rango;
		}

		public void setProbCercano(double probCercano) {
			this.probCercano = probCercano;
		}

		public double getProbCercano() {
			return probCercano;
		}

		public void setRutas(RutasInvalidas rutas) {
			this.rutas = rutas;
		}

		public RutasInvalidas getRutas() {
			return rutas;
		}

	}

	public static class Container {
		private AlgoritmoSeleccion as;
		private FactoriaCromosomas<Cromosoma> funcion;

		public void setAs(AlgoritmoSeleccion as) {
			this.as = as;
		}

		public AlgoritmoSeleccion getAs() {
			return as;
		}

		public void setFuncion(FactoriaCromosomas<Cromosoma> funcion) {
			this.funcion = funcion;
		}

		public FactoriaCromosomas<Cromosoma> getFuncion() {
			return funcion;
		}

	}

	public JPanel getGraphicPanel() {
		Plot2DPanel plot = new Plot2DPanel();
		double[] mejorG = Start.getMejorGeneracion();
		double[] mejorA = Start.getMejorAbsoluto();
		double[] mediaG = Start.getMediaGeneracion();
		plot.addLinePlot("Mejor Generacion", Color.green, mejorG);
		plot.addLinePlot("Mejor Absoluto", Color.blue, mejorA);
		plot.addLinePlot("Media Generación", Color.red, mediaG);
		plot.setLegendOrientation("SOUTH");
		plot.setAxisLabel(1, "Evaluación");
		plot.setAxisLabel(0, "Generación");
		return plot;
	}
}